package com.situ.shoplook.common.util;

import lombok.Getter;
import lombok.Setter;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.builder.annotation.ProviderContext;
import org.apache.ibatis.jdbc.SQL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings("all")
public class Xin {
    private static final Logger LOGGER = LoggerFactory.getLogger(Xin.class);

    private Xin() {
        //denied
    }

    private static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }

    private static void checkSqlSessionInfo(SqlSessionInfo ssi) {
        if (isEmpty(ssi.table())) {
            throw new RuntimeException("会话数据不可为空");
        }
    }

    //获取查询时的所有列名
    private static String getColumnsForSelect(SqlSessionInfo ssi) {
        String columns = ssi.columns();//查询列名
        if (isEmpty(columns)) {
            XinInfo xin = XinInfo.of(ssi.table());
            if (xin != null) {
                Collection<String> collection = xin.propertyToColumn.values();
                columns = String.join(",", collection);
            }
        }
        return columns;
    }

    //查询全部实体
    public static String selectAll(Map<String, Object> params, ProviderContext pc) {
        SqlSessionInfo ssi = currentSqlSessionInfo(params, pc);
        checkSqlSessionInfo(ssi);
        String columns = getColumnsForSelect(ssi);

        if (isEmpty(columns)) {
            columns = "*";
        }

        SQL sql = new SQL();
        sql.SELECT(columns).FROM(ssi.table());
        if (!isEmpty(ssi.where())) {
            sql.WHERE(ssi.where());
        }
        return sql.toString();
    }

    //根据主键查询唯一实例
    public static String selectByPrimaryKey(Map<String, Object> params, ProviderContext pc) {
        SqlSessionInfo ssi = currentSqlSessionInfo(params, pc);
        checkSqlSessionInfo(ssi);

        //筛选条件
        XinInfo xin = XinInfo.of(ssi.table());
        String primaryKey = xin.getPrimaryKey();
        if (isEmpty(primaryKey)) {
            primaryKey = "id";
        }

        String where = primaryKey + " = #{$primaryKey}";

        ssi = new SqlSessionInfo(ssi.table(), ssi.columns(), where);
        SqlSessionContext ssc = SqlSessionContextHolder.getSqlSessionContext();//此操作会清空本地线程变量
        if (ssc != null) {
            ssi.hold(ssc);
        } else {
            ssi.hold();
        }

        return selectAll(params, pc);
    }

    /**
     * 根据主键批量删除
     */
    public static String deleteByPrimaryKeys(Map<String, Object> params, ProviderContext pc) {
        SqlSessionInfo ssi = currentSqlSessionInfo(params, pc);
        checkSqlSessionInfo(ssi);

        List<?> pks = null;
        try {
            Object val = params.get("$primaryKeys");//获取所有主键
            if (val instanceof Collection<?> c) {
                pks = List.of(c);
            } else if (val.getClass().isArray()) {
                pks = List.of((Object[]) val);
            }
        } catch (Exception e) {
            LOGGER.error("缺少名称为$primaryKeys的参数", e);
            throw new RuntimeException(e);
        }

        String primaryKey = null;
        XinInfo info = XinInfo.of(ssi.table());
        if (info != null) {
            primaryKey = info.getPrimaryKey();
        }

        if (isEmpty(primaryKey)) {
            primaryKey = ssi.columns();//通过主键指定的列名
        }

        if (isEmpty(primaryKey)) {
            primaryKey = "id";
        }

        SQL sql = new SQL();
        sql.DELETE_FROM(ssi.table()).WHERE(primaryKey + " in "
                + applyForEach(pks, "$primaryKeys", "(", ")", ","));
        return sql.toString();
    }

    private static String applyForEach(List<?> args, String name, String open, String close, String separator) {
        Objects.requireNonNull(args);
        Objects.requireNonNull(open);
        Objects.requireNonNull(close);
        Objects.requireNonNull(separator);

        StringBuilder sql = new StringBuilder(open);
        for (int i = 0; i < args.size(); ++i) {
            sql.append("#{").append(name).append("[").append(i).append("]}").append(separator);
        }
        sql.delete(sql.length() - separator.length(), sql.length());//减掉最后一个分隔符
        sql.append(close);
        return sql.toString();
    }

    //自定义修改
    public static String update(Map<String, Object> params, ProviderContext pc) {
        SqlSessionInfo ssi = currentSqlSessionInfo(params, pc);
        checkSqlSessionInfo(ssi);

        SQL sql = new SQL();
        sql.UPDATE(ssi.table()).SET(ssi.columns()).WHERE(ssi.where());
        return sql.toString();
    }

    //根据主键修改实体
    public static String updateByPrimaryKey(Map<String, Object> params, ProviderContext pc) {
        SqlSessionInfo ssi = currentSqlSessionInfo(params, pc);
        checkSqlSessionInfo(ssi);

        Object entity = params.get("$entity");//模型实体
        //是否忽略实体null值
        boolean ignoreNulls = (boolean) params.get("$ignoreNulls");

        String columns = ssi.columns();//修改列信息
        if (isEmpty(columns)) {//如果未指定列，则从XinInfo中获取
            XinInfo xin = XinInfo.of(ssi.table());
            if (xin != null) {
                List<String> sets = new ArrayList<>();
                Collection<String> cols = xin.getPropertyToColumn().values();
                for (String col : cols) {
                    String property = xin.getProperty(col);
                    PropertyDescriptor pd = xin.getPropertyDescriptor(property);
                    if (ignoreNulls) {//忽略模型类的中null值
                        try {
                            Object value = pd.getReadMethod().invoke(entity);
                            if (value != null) {
                                sets.add(col + "=" + "#{$entity." + property + "}");
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        sets.add(col + "=" + "#{$entity." + property + "}");
                    }
                }
                columns = String.join(",", sets);
            }
        }

        //筛选条件
        String where = ssi.where();//用户可以指定条件，但如果未指定查询条件，则不进行全体修改，而是根据主键进行查询
        if (isEmpty(where)) {
            XinInfo xin = XinInfo.of(ssi.table());
            String primaryKey = xin.getPrimaryKey();//主键列
            if (isEmpty(primaryKey)) {
                primaryKey = "id";
            }
            String property = xin.getProperty(primaryKey);
            where = primaryKey + " = #{$entity." + property + "}";
        }

        SQL sql = new SQL();
        sql.UPDATE(ssi.table()).SET(columns).WHERE(where);
        return sql.toString();
    }

    public static String insert(Map<String, Object> params, ProviderContext pc) {
        SqlSessionInfo ssi = currentSqlSessionInfo(params, pc);
        checkSqlSessionInfo(ssi);

        XinInfo xin = XinInfo.of(ssi.table());
        //是否忽略实体null值
        boolean ignoreNulls = (boolean) params.get("$ignoreNulls");

        String columns = getColumnsForSelect(ssi);//全部列名，可能包含主键，因为主键不一定都是自动生成的，若要自动生成，置null即可
        List<String> insertCols = new ArrayList<>();
        List<String> values = new ArrayList<>();

        for (String column : columns.split(",")) {
            String property = xin.getProperty(column);
            if (ignoreNulls) {
                if (property != null) {
                    insertCols.add(column);
                    values.add("#{$entity." + property + "}");
                }
            } else {
                insertCols.add(column);
                values.add("#{$entity." + property + "}");
            }
        }

        SQL sql = new SQL();
        sql.INSERT_INTO(ssi.table()).INTO_COLUMNS(String.join(",", insertCols))
                .INTO_VALUES(String.join(",", values));
        return sql.toString();
    }

    private static final Map<Class<?>, SqlSessionInfo> SQLSESSION_INFO_MAP = new ConcurrentHashMap<>();

    //获取当前会话信息
    private static SqlSessionInfo currentSqlSessionInfo(Map<String, Object> params, ProviderContext pc) {
        //mapper方法
        Method method = pc.getMapperMethod();
        //mapper接口类型
        Class<?> clazz = pc.getMapperType();

        SqlSessionInfo ssi = SqlSessionInfo.empty();

        //1.尝试从本地线程上下文中获取会话数据，实时动态获取
        SqlSessionContext ssc = SqlSessionContextHolder.getSqlSessionContext();
        if (ssc != null) {
            ssi = ssi.patch(ssc.getSqlSessionInfo());
        }

        //2.从mapper方法参数中获取会话信息，实时动态获取。参数使用@Param("$Table)，@Param("$Columns)，@Param("$Where)
        ssi = ssi.patch(getSqlSessionInfoFromParams(params));

        //3.尝试从mapper接口实例中获取会话数据，实时动态获取。
        Object mapper = null;
        if (ssc != null) {
            mapper = ssc.getMapper();
        }

        if (mapper == null) {
            try {
                mapper = params.get("$Mapper");//若找不到不会返回null，而是直接报异常
            } catch (Exception e) {
                mapper = detectMapper(params, pc);
            }
        }

        if (mapper != null) {
            ssi = ssi.patch(getSqlSessionInfoFromMapper(mapper));
        }

        //4.尝试从mapperClass静态方法获取会话数据
        ssi = ssi.patch(getSqlSessionInfoFromMapperClass(clazz));

        //从缓存中获取
        SqlSessionInfo old = SQLSESSION_INFO_MAP.get(clazz);
        if (old != null) {
            return ssi.patch(old);
        }

        //5.从mapper接口方法的注解上获取会话数据，可获取where数据
        old = SqlSessionInfo.empty();
        old = old.patch(getSqlSessionInfoFromAnnotation(method));

        //6.从模型类的注解上获取会话信息，仅用于获取列名和表名，不能获取where条件
        old = old.patch(getSqlSessionInfoFromEntityClass(clazz, mapper));
        SQLSESSION_INFO_MAP.putIfAbsent(clazz, old);

        return ssi.patch(old);
    }

    /**
     * 从模型类上获取会话信息并缓存
     *
     * @param mapperClass mapper接口
     * @return 会话信息
     */
    private static SqlSessionInfo getSqlSessionInfoFromEntityClass(Class<?> mapperClass, Object mapper) {
        Class<?> entityClass = null;//模型类
        MethodHandles.Lookup lookup = MethodHandles.lookup();

        //1.尝试从继承的XinMapper接口中获取Entity类型
        Type[] ifs = mapperClass.getGenericInterfaces();//获取mapperClass所继承的所有接口
        for (Type ifc : ifs) {
            if (ifc instanceof ParameterizedType pt && pt.getRawType().equals(XinMapper.class)) {
                //获取XinMapper接口指定的泛型参数类型，第一个是模型类类型，第二个是主键类型
                Type[] gts = pt.getActualTypeArguments();
                entityClass = (Class<?>) gts[0];
            }
        }

        //2.尝试从mapperClass接口中获取@Meta注解中的where属性，@Meta注解的where属性用于指定Entity的全限定名
        if (entityClass == null) {
            if (mapperClass.isAnnotationPresent(Meta.class)) {
                Meta meta = mapperClass.getAnnotation(Meta.class);
                String entityClassName = meta.where();//实体类的全限定名
                try {
                    entityClass = Class.forName(entityClassName);
                } catch (ClassNotFoundException e) {
                    //do nothing...
                }
            }
        }

        //3.尝试从mapper实例的getEntityClass获取Entity类型
        if (entityClass == null && mapper != null) {
            try {
                MethodHandle method = lookup.findVirtual(mapperClass, "getEntityClass",
                        MethodType.methodType(Class.class));
                if (method != null) {
                    entityClass = (Class<?>) method.invoke(mapper);
                }
            } catch (Throwable e) {
                //LOGGER.error("调用实例方法时异常", e);
            }
        }

        //4.尝试从mapperClass类的静态方法getEntityClass获取泛型
        if (entityClass == null) {
            try {
                MethodHandle method = lookup.findStatic(mapperClass,
                        "getEntityClass", MethodType.methodType(Class.class));
                if (method != null) {
                    entityClass = (Class<?>) method.invoke();
                }
            } catch (Throwable e) {
                //LOGGER.error("调用实例方法时异常", e);
            }
        }

        String table = null;//表名
        String primaryKey = null;//主键列名称

        //5.获取会话数据，及表和实体类的相关映射信息
        if (entityClass != null) {//模型类
            XinInfo xin = XinInfoFactory.getXinInfo(entityClass);
            return new SqlSessionInfo(xin.getTableName(), null, null);
        }

        return null;
    }

    /**
     * 从mapper方法的参数中获取会话数据
     */
    private static SqlSessionInfo getSqlSessionInfoFromParams(Map<String, Object> params) {
        String table;
        String columns;
        String where;

        try {
            table = params.get("$Table").toString();
            columns = params.get("$Columns").toString();
            where = params.get("$Where").toString();
        } catch (Exception e) {
            return null;
        }
        return new SqlSessionInfo(table, columns, where);
    }

    /**
     * 从mapper方法标注的注解上获取会话信息
     *
     * @param method mapper方法
     * @return 会话数据
     */
    private static SqlSessionInfo getSqlSessionInfoFromAnnotation(Method method) {
        if (method.isAnnotationPresent(Meta.class)) {
            Meta meta = method.getAnnotation(Meta.class);
            String table = meta.table();
            String columns = meta.columns();
            String where = meta.where();
            return new SqlSessionInfo(table, columns, where);
        }
        return null;
    }

    //获取mapper实例。注意：mapper实例通常是标注了@Mapper接口的动态代理实例
    private static Object detectMapper(Map<String, Object> params, ProviderContext pc) {
        Class<?> clazz = pc.getMapperType();//mapper接口类型
        for (Object param : params.values()) {
            Class<?>[] ifs = param.getClass().getInterfaces();
            for (Class<?> ifc : ifs) {
                //如果接口标注了@Mapper注解，或实现了clazz接口
                if (ifc.isAnnotationPresent(Mapper.class) || ifc.isAssignableFrom(clazz)) {
                    return param;
                }
            }
        }
        return null;
    }

    /**
     * 从mapper实例中获取会话数据
     *
     * @param mapper mapper实例
     * @return 会话数据
     */
    private static SqlSessionInfo getSqlSessionInfoFromMapper(Object mapper) {
        String table = getSqlSessionInfoItemFromMapper(mapper, "getTable");
        String columns = getSqlSessionInfoItemFromMapper(mapper, "getColumns");
        String where = getSqlSessionInfoItemFromMapper(mapper, "getWhere");
        return new SqlSessionInfo(table, columns, where);
    }

    /**
     * 通过Mapper接口静态方法获取会话数据
     *
     * @param mapperClass mapper接口
     * @return 会话数据
     */
    private static SqlSessionInfo getSqlSessionInfoFromMapperClass(Class<?> mapperClass) {
        String table = getSqlSessionInfoItemFromMapperClass(mapperClass, "getTable");
        String columns = getSqlSessionInfoItemFromMapperClass(mapperClass, "getColumns");
        String where = getSqlSessionInfoItemFromMapperClass(mapperClass, "getWhere");
        return new SqlSessionInfo(table, columns, where);
    }


    //通过静态方法获取table,columns和where
    private static String getSqlSessionInfoItemFromMapper(Object mapper, String meta) {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            MethodHandle method = lookup.findVirtual(mapper.getClass(), meta,
                    MethodType.methodType(String.class));

            if (method != null) {
                return (String) method.invoke(mapper);
            }
        } catch (Throwable e) {
            //LOGGER.error("调用实例方法时异常", e);
        }
        return null;
    }


    //通过静态方法获取table,columns和where
    private static String getSqlSessionInfoItemFromMapperClass(Class<?> mapperClass, String meta) {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            MethodHandle method = lookup.findStatic(mapperClass, meta, MethodType.methodType(String.class));
            if (method != null) {
                return (String) method.invoke();
            }
        } catch (Throwable e) {
            //LOGGER.error("调用静态方法时异常", e);
        }
        return null;
    }


    /**
     * 会话信息
     *
     * @param table   表名
     * @param columns 列名
     * @param where   筛选条件
     */
    public record SqlSessionInfo(String table, String columns, String where) {
        //补丁
        public SqlSessionInfo patch(SqlSessionInfo ssi) {
            if (ssi == null) {
                return this;
            }
            return new SqlSessionInfo(isEmpty(table) ? ssi.table() : table,
                    isEmpty(columns) ? ssi.columns() : columns,
                    isEmpty(where) ? ssi.where() : where);
        }

        public SqlSessionContext hold(SqlSessionContext ssc) {
            ssc.setSqlSessionInfo(this);
            SqlSessionContextHolder.setSqlSessionContext(ssc);
            return ssc;
        }

        //加入到本地线程变量中
        public SqlSessionContext hold() {
            return hold(new SqlSessionContext());
        }

        public static SqlSessionInfo empty() {
            return new SqlSessionInfo(null, null, null);
        }

        public boolean isFull() {
            return !isEmpty(table) && !isEmpty(columns) && !isEmpty(where);
        }
    }

    //会话上下文
    @Getter
    @Setter
    public static class SqlSessionContext {
        private Object mapper;//mapper实例
        private SqlSessionInfo sqlSessionInfo;
    }

    //会话上下文持有器
    public static class SqlSessionContextHolder {
        private static final ThreadLocal<SqlSessionContext> threadLocal = new ThreadLocal<>();

        public static SqlSessionContext getSqlSessionContext() {
            SqlSessionContext sc = threadLocal.get();
            threadLocal.remove();
            return sc;
        }

        public static void setSqlSessionContext(SqlSessionContext sqlSessionContext) {
            threadLocal.set(sqlSessionContext);
        }
    }

    //模型类与表的映射信息生成工厂
    public static class XinInfoFactory {
        private static final Map<Class<?>, XinInfo> CASHES = new ConcurrentHashMap<>();

        public static XinInfo getXinInfo(Class<?> entityClass) {
            String table = null;
            String primaryKey = null;

            if (entityClass.isAnnotationPresent(Meta.class)) {
                Meta meta = entityClass.getAnnotation(Meta.class);
                table = meta.table();//表名
                primaryKey = meta.where();//使用where属性指定主键列名
            }

            if (isEmpty(table)) {
                table = camelToUnderscore(entityClass.getSimpleName());
            }

            if (isEmpty(primaryKey)) {
                primaryKey = "id";
            }

            return getXinInfo(entityClass, table, primaryKey);
        }

        private static Field getField(Class<?> clazz, String fieldName) {
            Field field = null;
            try {
                field = clazz.getDeclaredField(fieldName);
            } catch (Exception e) {
                //do nothing...
            }

            if (field == null) {
                try {
                    field = clazz.getField(fieldName);
                } catch (Exception e) {
                    //do nothing...
                }
            }
            return field;
        }

        public static XinInfo getXinInfo(Class<?> entityClass, String table, String primaryKey) {
            Objects.requireNonNull(entityClass);

            XinInfo info = CASHES.get(entityClass);
            if (info != null) {
                return info;
            }

            info = new XinInfo(table, primaryKey);
            try {
                BeanInfo bi = Introspector.getBeanInfo(entityClass);
                PropertyDescriptor[] pds = bi.getPropertyDescriptors();
                for (PropertyDescriptor pd : pds) {
                    String property = pd.getName();//模型类属性名
                    if (!"class".equals(property)) {
                        Field field = getField(entityClass, property);
                        if (field != null) {
                            if (field.isAnnotationPresent(Meta.class)) {
                                Meta meta = field.getAnnotation(Meta.class);
                                String column = meta.columns();//使用@Meta标记指定的列名
                                if (!"false".equals(meta.where())) {
                                    if (!isEmpty(column)) {
                                        info.setMapping(column, pd);
                                    } else {
                                        info.setMapping(camelToUnderscore(property), pd);
                                    }
                                }
                            } else {
                                info.setMapping(camelToUnderscore(property), pd);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                LOGGER.error("使用反射获取bean信息时异常", e);
                throw new RuntimeException(e);
            }
            CASHES.putIfAbsent(entityClass, info);
            return info;
        }
    }

    //表与模型类的映射信息
    @Getter
    public static class XinInfo {
        private static final Map<String, XinInfo> CASHES = new ConcurrentHashMap<>();
        private final String tableName;//表名
        private final String primaryKey;//主键列名
        private final Map<String, String> columnToProperty = new HashMap<>();//通过列名找属性名
        private final Map<String, String> propertyToColumn = new HashMap<>();//通过属性名找列名
        private final Map<String, PropertyDescriptor> propertyToDescriptor = new HashMap<>();//通过属性名找属性描述符

        public XinInfo(String tableName, String primaryKey) {
            this.tableName = tableName;
            this.primaryKey = primaryKey;
            CASHES.put(this.tableName, this);
        }

        //通过列名找属性名
        public String getProperty(String column) {
            return columnToProperty.get(column);
        }

        //通过属性名找列名
        public String getColumn(String property) {
            return propertyToColumn.get(property);
        }

        //获取属性描述符
        public PropertyDescriptor getPropertyDescriptor(String property) {
            return propertyToDescriptor.get(property);
        }

        public void setMapping(String column, String property) {
            columnToProperty.put(column, property);
            propertyToColumn.put(property, column);
        }

        public void setMapping(String column, PropertyDescriptor pd) {
            setMapping(column, pd.getName());
            propertyToDescriptor.put(pd.getName(), pd);
        }

        public static XinInfo of(String tableName) {
            return CASHES.get(tableName);
        }
    }


    /**
     * 标注在mapper接口方法上的注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
    public @interface Meta {
        String table() default "";

        String columns() default "";

        String where() default "";
    }

    /**
     * 基础Mapper
     *
     * @param <T>  实体类之类型
     * @param <ID> 主键之类型
     */
    public interface XinMapper<T, ID> {
        @SelectProvider(type = Xin.class, method = "selectByPrimaryKey")
        T selectByPrimaryKey(@Param("$primaryKey") ID id);

        //插入
        default int insert(T entity) {
            return insertSelective(entity, false);
        }

        //插入非空项
        @InsertProvider(type = Xin.class, method = "insert")
        int insertSelective(@Param("$entity") T entity,
                            @Param("$ignoreNulls") boolean ignoreNulls);

        //更新
        default int updateByPrimaryKey(T entity) {
            return updateByPrimaryKeySelective(entity, true);
        }

        //更新非空项
        @UpdateProvider(type = Xin.class, method = "updateByPrimaryKey")
        int updateByPrimaryKeySelective(@Param("$entity") T entity,
                                        @Param("$ignoreNulls") boolean ignoreNulls);

        //根据主键删除
        default int deleteByPrimaryKey(ID primaryKey) {
            return deleteByPrimaryKeys(List.of(primaryKey));
        }

        //根据主键删除
        @DeleteProvider(type = Xin.class, method = "deleteByPrimaryKeys")
        int deleteByPrimaryKeys(@Param("$primaryKeys") List<ID> primaryKeys);
    }

    private static final Pattern UNDERSCORE_TO_CAMEL_PATTERN = Pattern.compile("_(\\w)\\w*?");

    //下划线转驼峰式
    public static String underscoreToCamel(String input) {
        Matcher matcher = UNDERSCORE_TO_CAMEL_PATTERN.matcher(input);
        StringBuilder sb = new StringBuilder();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }


    private static final Pattern CAMEL_TO_UNDERSCORE_PATTERN = Pattern.compile("([A-Z])");

    //驼峰式转下划线
    public static String camelToUnderscore(String input) {
        Matcher matcher = CAMEL_TO_UNDERSCORE_PATTERN.matcher(input);
        StringBuilder sb = new StringBuilder();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(1).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}